package fr.alma.coeur;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Properties;

import fr.alma.interfacesPlugins.PluginBase;
import fr.alma.interfacesPlugins.PluginEntree;
import fr.alma.interfacesPlugins.PluginSortie;

/**
 * Classe d'activation des plugins de maniere paresseuse.
 * @author AUBERT Sebastien
 * @author BOUVET Frederic
 * @author BRAUD Jeremy
 * @author KROMMENHOEK Cedric
 *
 */
public class Activateur {

	/**
	 * Liste des plugins charges.
	 */
	private ArrayList<PluginBase> plugins;
	/**
	 * Liste de liens.
	 */
	private ArrayList<String[]> liens;
	/**
	 * Le loggeur.
	 */
	private Loggeur loggeur;

	/**
	 * Constructeur.
	 */
	public Activateur() {
		this.plugins = new ArrayList<PluginBase>();
		this.liens = new ArrayList<String[]>();
		this.loggeur = Loggeur.getLoggeur();
	}
	
	/**
	 * Methode retournant le nombre de liens.
	 * @return le nombre de liens
	 */
	public int nombreLiens() {
		return this.liens.size();
	}
	
	/**
	 * Test d'un chargement precedent.
	 * @param nomPlugin le nom du plugin a tester
	 * @return vrai si le plugin est deja charge 
	 */
	public boolean estCharge(String nomPlugin) {
		for (Iterator<PluginBase> iterator = this.plugins.iterator(); iterator.hasNext();) {
			PluginBase plugin = iterator.next();
			if (plugin.getClass().getName().endsWith(nomPlugin)) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Teste si le plugin est de type PluginBase.
	 * @param classe la classe du plugin
	 * @return vrai si le type est PluginBase
	 */
	public static boolean estPluginBase(Class<?> classe) {
		Class<?>[] interfaces = classe.getInterfaces();
		for (int i = 0; i < interfaces.length; i++) {
			Class<?> interfacePlugin = interfaces[i];
			if (interfacePlugin.getCanonicalName().endsWith("PluginEntree")
					|| interfacePlugin.getCanonicalName().endsWith("PluginTransformateur")
					|| interfacePlugin.getCanonicalName().endsWith("PluginSortie")) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Teste si le plugin est de type PluginTransformateur.
	 * @param classe la classe du plugin
	 * @return vrai si le type est PluginTransformateur
	 */
	public static boolean estPluginTransformateur(Class<?> classe) {
		Class<?>[] interfaces = classe.getInterfaces();
		for (int i = 0; i < interfaces.length; i++) {
			Class<?> interfacePlugin = interfaces[i];
			if (interfacePlugin.getCanonicalName().endsWith("PluginTransformateur")) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Methode de recuperation de la propriete du type d'interface.
	 * @param classe la classe dont on recherche le type
	 * @return le type d'interface
	 */
	public static String getProprieteInterface(Class<?> classe) {
		String nomInterface;
		for (Class<?> i : classe.getInterfaces()) {
			nomInterface = i.getName();
			if (nomInterface.endsWith("PluginEntree")) {
				return "entree";
			} else if (nomInterface.endsWith("PluginSortie")) {
				return "sortie";
			} else if (nomInterface.endsWith("PluginTransformateur")) {
				return "transformateur";
			}
		}
		return null;
	}
	
	/**
	 * Retourne la liste des plugins connectes a un plugin d'entree.
	 * @param pluginEntree le nom du plugin d'entree
	 * @return la liste des plugins connectes
	 */
	public ArrayList<String> getPluginsConnectes(String pluginEntree) {
		ArrayList<String> liste = new ArrayList<String>();
		for (String[] lien : this.liens) {
			if (lien[0].equals(pluginEntree)) {
				liste.add(lien[1]);
			}
		}
		return liste;
	}
	
	/**
	 * Instanciation de la classe d'un plugin.
	 * @param nomPlugin le nom du plugin
	 * @return le plugin cree
	 */
	private PluginBase ajoutPlugin(String nomPlugin) {
		try {
			Class<?> classe = Class.forName(nomPlugin);
			if (Activateur.estPluginBase(classe)) {
				return this.instanciationPlugin(classe);
			}
		} catch (ClassNotFoundException e1) {
			e1.printStackTrace();
		}catch (IllegalArgumentException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Recherche du constructeur par defaut et instanciation.
	 * @param classe la classe du plugin a instancier
	 * @return l'instance du plugin
	 */
	private PluginBase instanciationPlugin(Class<?> classe) {
		Constructor<?>[] constructeurs = classe.getConstructors();
		for (int i = 0; i < constructeurs.length; i++) {
			Constructor<?> constructeur = constructeurs[i];
			if (constructeur.getParameterTypes().length == 0) {
				this.loggeur.log("Activateur: Instantiation du plugin " + classe.getName());
				return this.appelConstructeur(constructeur);
			}
		}
		return null;
	}
	
	/**
	 * Methode d'appel au constructeur du plugin desire.
	 * @param constructeur le constructeur
	 * @return l'instance du plugin
	 */
	private PluginBase appelConstructeur(Constructor<?> constructeur) {
		PluginBase plugin;
		try {
			plugin = (PluginBase) constructeur.newInstance();
			this.plugins.add(plugin);
			return plugin;
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}	
		return null;
	}
	
	/**
	 * Methode renvoyant le plugin cree par son nom.
	 * @param nomPlugin le nom du plugin
	 * @return le plugin recherche
	 */
	private PluginBase getPlugin(String nomPlugin) {
		for (PluginBase plugin : this.plugins) {
			if (plugin.getClass().getName().equals(nomPlugin)) {
				return plugin;
			}
		}
		return null;
	}
	
	/**
	 * Methode d'activation des plugins externes. 
	 * @param nomPlugin le nom du plugin a activer
	 * @param loader l'URLClassLoader necessaire a l'instanciation
	 * @return les proprietes du plugin active
	 */
	public Properties ajoutPluginExterne(String nomPlugin, URLClassLoader loader) {
		Properties p = new Properties();
		try {
			Class<?> classe = Class.forName(nomPlugin, true, loader);
			if (Activateur.estPluginBase(classe)) {
				PluginBase plugin = this.instanciationPlugin(classe);
				
				// Recuperation des informations du plugin
				String nomClasse = classe.getName();
				int indexSeparation = nomClasse.lastIndexOf(".");
				String nomPackage = nomClasse.substring(0, indexSeparation);
				String nom = nomClasse.substring(indexSeparation + 1);

				// Definition des proprietes
				p.setProperty("nom", nom);
				p.setProperty("package", nomPackage);
				p.setProperty("categorie", plugin.getCategorie());
				p.setProperty("description", plugin.getDescription());
				p.setProperty("type", Activateur.getProprieteInterface(classe));
			}
			
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return p;
	}

	/**
	 * Methode d'activation des plugins.
	 * @param nomPlugin le nom du plugin a activer
	 */
	public void activerPlugin(String nomPlugin) {
		PluginEntree pluginEntree;
		if (!estCharge(nomPlugin)) {
			pluginEntree = (PluginEntree) this.ajoutPlugin(nomPlugin);
		} else {
			pluginEntree = (PluginEntree) this.getPlugin(nomPlugin);
		}
		
		this.loggeur.log("Activateur: Demarrage du plugin " + pluginEntree.getClass().getName());
		pluginEntree.start();
		
		PluginSortie pluginSortie;
		for (String plugin : this.getPluginsConnectes(nomPlugin)) {
			if (!estCharge(plugin)) {
				pluginSortie = (PluginSortie) this.ajoutPlugin(plugin);
			} else {
				pluginSortie = (PluginSortie) this.getPlugin(plugin);
			}
			pluginEntree.ajoutObservateur(pluginSortie);

			this.loggeur.log("Activateur: Demarrage du plugin " + pluginSortie.getClass().getName());
			pluginSortie.start();
			
			if (Activateur.estPluginTransformateur(pluginSortie.getClass())) {
				pluginEntree = (PluginEntree) pluginSortie;
			}
		}
	}

	/**
	 * Test d'existance d'un lien entre deux plugins.
	 * @param nomPluginEntree le nom du plugin d'entree
	 * @param nomPluginSortie le nom du plugin de sortie
	 * @return vrai si le lien existe
	 */
	private boolean existeLien(String nomPluginEntree, String nomPluginSortie) {
		for (String[] lien : this.liens) {
			if ((lien[0].equals(nomPluginEntree)) && (lien[1].equals(nomPluginSortie))) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Ajout d'un lien entre deux plugins.
	 * @param nomPluginEntree le nom du plugin d'entree
	 * @param nomPluginSortie le nom du plugin de sortie
	 */
	public void ajoutLien(String nomPluginEntree, String nomPluginSortie) {
		if (!this.existeLien(nomPluginEntree, nomPluginSortie)) {
			String[] lien = new String[2];
			lien[0] = nomPluginEntree;
			lien[1] = nomPluginSortie;
			this.liens.add(lien);
		}
	}

	/**
	 * Suppression d'un lien entre deux plugins.
	 * @param nomPluginEntree le nom du plugin d'entree
	 * @param nomPluginSortie le nom du plugin de sortie
	 */
	public void suppressionLien(String nomPluginEntree, String nomPluginSortie) {
		for (Iterator<String[]> iterator = this.liens.iterator(); iterator.hasNext();) {
			String[] lien = iterator.next();
			if ((lien[0].equals(nomPluginEntree)) && (lien[1].equals(nomPluginSortie))) {
				iterator.remove();
				break;
			}
		}
	}

	/**
	 * Arrete le plugin rentre en parametre et les plugins relies.
	 * @param nomPlugin le nom du plugin
	 */
	public void arreterPlugin(String nomPlugin) {
		PluginBase plugin = this.getPlugin(nomPlugin);
		for (String plug : this.getPluginsConnectes(nomPlugin)) {
			this.arreterPlugin(plug);
		}
		this.loggeur.log("Activateur: Arret du plugin " + plugin.getClass().getName());
		plugin.stop();
	}
	
}
